﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LiftEmulator.Model;
using LiftEmulator.View.Helpers;

namespace LiftEmulator.View
{
    /// <summary>
    /// Анимационное представление.
    /// Отображает передвижение лифта и текущие состояние очереди в консоле
    /// </summary>
    public class AnimatedView
    {
        /// <summary>
        /// Строка с которой неоходимо начинать выводить информацию 
        /// используется, для того, чтобы можно было отображать на экране сразу несколько
        /// разных представлений
        /// </summary>
        private int lineStart;

        /// <summary>
        /// Количество этажей, запоминаем для удобства вычисления строки
        /// </summary>
        private int numberOfFloor;


        /// <summary>
        /// Длина лифта (используется для расчета левого смещения)
        /// </summary>
        private int liftLength;


        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="lineStart">Строка с которой неоходимо начинать выводить информацию</param>
        /// <param name="building">Модель здания, используется для получения нужной информации и подписки на необходимые события</param>
        public AnimatedView(int lineStart, Building building)
        {
            this.lineStart = lineStart;
            numberOfFloor = building.Floors.Length;

            // подписка на события
            foreach (var floor in building.Floors) floor.FloorChange += new EventHandler<Floor.FloorChangeEventArgs>(DrawFloorQueue);
            building.Lift.ChangeFloor += new EventHandler<Lift.ChangeFloorEventArgs>(Lift_ChangeFloor);
            building.Lift.Boarding += new EventHandler<Lift.BoardingEventArgs>(Lift_Boarding);
            
            
            // длина лифта зависит от его вместимости (плюс по бокам отображаем [ ], плюс стрелка)
            liftLength = 2 + building.Lift.MaxCapacityOfTheElevator + 2;

            // отображения каркаса и первоначального состояния
            var liftEntry = new string(' ', liftLength);
            Console.SetCursorPosition(0, lineStart);
            foreach (var floor in building.Floors.Reverse())
            {
                Console.WriteLine("{0} - " + liftEntry + " : ", floor.Number);
            }
            DrawLift(building.Lift); 
        }

        #region Обработчики событий и функции отображения анимации
        
        /// <summary>
        /// Подписчик на событие загрузки/разгрузки лифта
        /// Перерисовывает лифт и отображает стрелку
        /// </summary>
        /// <param name="sender">отправитель</param>
        /// <param name="e">аргументы</param>
        private void Lift_Boarding(object sender, Lift.BoardingEventArgs e)
        {
            var lift = (Lift)sender;
            DrawLift(lift, isIn : e.IsBoarding);
        }


        /// <summary>
        /// Подписчик на событие перемещение лифта
        /// Стирает лифт на предыдущем этаже и рисует на новом
        /// </summary>
        /// <param name="sender">отправитель</param>
        /// <param name="e">аргументы</param>
        private void Lift_ChangeFloor(object sender, Lift.ChangeFloorEventArgs e)
        {
            var lift = (Lift)sender;

            DrawLift(lift, floor: e.PrevFloor, clear: true);
            DrawLift(lift);
        }


        /// <summary>
        /// Рисует лифт
        /// </summary>
        /// <param name="lift">лифт</param>
        /// <param name="floor">этаж, если не задан - этаж на котором находится лифт</param>
        /// <param name="isIn">загрузка/выгрузка (не обязательно) (при указании рисуется стрелка)</param>
        /// <param name="clear">очистка/рисование (не обязательно)</param>
        private void DrawLift(Lift lift, Floor floor = null, bool? isIn = null, bool clear = false)
        {
            const int x = 5;
            const int arrowLen = 2;            
            var y = lineStart + (numberOfFloor - (floor ?? lift.CurrentFloor).Number);

            if (!clear)
            {
                // формируем строку с отображением лифта [231 ]
                var liftEntry = string.Join("", lift.Inner.Select(people => people.TargetFloor.Number.ToString()));
                liftEntry += new string(' ', lift.MaxCapacityOfTheElevator - liftEntry.Length);
                liftEntry = "[" + liftEntry + "]";

                // если в лифт кто-нибудь заходит/выходит - отображается стрелочка
                if (isIn != null)
                {
                    var arrow = isIn != null ? (isIn.Value ? "<-" : "->") : "  ";
                    DrawHelper.Write(x, y, liftEntry + arrow);
                }
                else
                {
                    DrawHelper.Write(x, y, liftEntry + new string(' ', arrowLen));
                }                
            }
            else
            {
                DrawHelper.ClearRow(x, y, liftLength);
            }
        }


        /// <summary>
        /// Отображение очереди ожидающих лифт
        /// </summary>
        /// <param name="sender">отправитель</param>
        /// <param name="e">аргументы</param>
        private void DrawFloorQueue(object sender, Floor.FloorChangeEventArgs e)
        {
            // слева отображается лифт и номера этажей
            var leftpref = 7 + liftLength; 
            var floor = (Floor) sender;
            
            // переворачиваем, чтобы этажи отображались снизу вверх
            var floorLineIndex = lineStart + (numberOfFloor - floor.Number);
            
            // так как изменение очереди может быть только на единицу, то добавляем ещё
            // два пробела для затирания оставшихся цифр
            var line = string.Join(" ", e.NewQueue.Select(people=>people.TargetFloor.Number)) + "  ";

            DrawHelper.Write(leftpref, floorLineIndex, line);
        }

        #endregion
    }
}
